package com.study.sbsummary.config.database.shadow;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.TableNameParser;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.study.sbsummary.context.RequestContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Data
@Slf4j
@Configuration
public class ShadowInterceptor implements InnerInterceptor {
    public static final char TABLE_SEPARATOR = '`';
    public static final char DB_TBL_SEPARATOR = '.';

    private static final Map<String, String> shadowTbls = new ConcurrentHashMap<>();

    //是否启用全链路压测的开关，默认开启
    private static boolean supportOnlineTest = true;

    //判断是影子库还是影子表，默认影子库，合理取值database或table
    private static String shadowType = "database";

    private static boolean shadowDatabase = true;

    private static boolean shadowTable = false;

    @Value("${plog.online_test:true}")
    public void setSupportOnlineTest(boolean supportOnlineTest) {
        ShadowInterceptor.supportOnlineTest = supportOnlineTest;
    }

    public static boolean isSupportOnlineTest() {
        return supportOnlineTest;
    }

    public static String shadowType() {
        return shadowType;
    }

    @Value("${plog.shadow.type:database}")
    public void setShadowType(String shadowType) {
        ShadowInterceptor.shadowType = shadowType;
        shadowDatabase = shadowType.equals("database");
        shadowTable = !shadowDatabase;
    }

    @PostConstruct
    public void init() {
        shadowDatabase &= supportOnlineTest;
        shadowTable &= supportOnlineTest;
        log.info("====================== online test info begin ======================");
        if (supportOnlineTest) {
            log.info("               supporting online test with shadow type: {}", shadowType);
        } else {
            log.info("                 un-supporting online test");
        }
        log.info("====================== online test info end ======================");
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        if (RequestContext.isOnlineTest()) {
            PluginUtils.MPStatementHandler mpStatementHandler = PluginUtils.mpStatementHandler(sh);
            PluginUtils.MPBoundSql boundSql = mpStatementHandler.mPBoundSql();
            boundSql.sql(changeTableNameToPressureTest(boundSql.sql()));
        }
    }

    protected String changeTable(String sql) {
        TableNameParser parser = new TableNameParser(sql);
        List<TableNameParser.SqlToken> names = new ArrayList();
        parser.accept(names::add);
        StringBuilder builder = new StringBuilder();
        int last = 0;

        TableNameParser.SqlToken name;
        for (Iterator var6 = names.iterator(); var6.hasNext(); last = name.getEnd()) {
            name = (TableNameParser.SqlToken) var6.next();
            int start = name.getStart();
            if (start != last) {
                builder.append(sql, last, start);
                builder.append(shadowName(name.getValue()));
            }
        }

        if (last != sql.length()) {
            builder.append(sql.substring(last));
        }

        return builder.toString();
    }

    String changeTableNameToPressureTest(String sql) {
        TableNameParser parser = new TableNameParser(sql);
        List<TableNameParser.SqlToken> names = new ArrayList<>();
        parser.accept(names::add);
        int last = 0;
        StringBuilder builder = new StringBuilder();
        for (TableNameParser.SqlToken name : names) {
            int start = name.getStart();
            if (start != last) {
                builder.append(sql, last, start);
                String value = name.getValue();
                builder.append(dynamicTable(value));
            }
            last = name.getEnd();
        }

        if (last != sql.length()) {
            builder.append(sql.substring(last));
        }

        return builder.toString();
    }

    private String dynamicTable(String tableName) {
        if (shadowTbls.containsKey(tableName)) {
            return shadowTbls.get(tableName);
        }
        String newName = changeTableName(tableName);
        shadowTbls.put(tableName, newName);
        return newName;
    }

    private String changeTableName(String tableName) {
        if (TABLE_SEPARATOR == tableName.charAt(0)) {
            StringBuilder sb = new StringBuilder();
            sb.append(TABLE_SEPARATOR);
            if (TABLE_SEPARATOR == tableName.charAt(tableName.length() - 1)) {
                sb.append(changeTable(tableName.substring(1, tableName.length() - 1)));
                sb.append(TABLE_SEPARATOR);
            } else {
                sb.append(changeTable(tableName.substring(1)));
            }
            return sb.toString();
        } else {
            return shadowName(tableName);
        }
    }

    public static boolean isShadowName(String name) {
        return name.startsWith(ShadowConstants.PRESSURE_TEST_PREFIX);
    }

    public static String shadowName(String name) {
        if (shadowTbls.containsKey(name)) {
            return shadowTbls.get(name);
        }
        String nTbl = name;
        int dotIndex = name.indexOf(DB_TBL_SEPARATOR);
        if (dotIndex >= 0) {
            String dbName = name.substring(0, dotIndex);
            if (TABLE_SEPARATOR == dbName.charAt(dotIndex - 1)) {
                dbName = dbName.substring(0, dotIndex) + DB_TBL_SEPARATOR;
            } else {
                dbName += DB_TBL_SEPARATOR;
            }
            if (TABLE_SEPARATOR == name.charAt(dotIndex + 1)) {
                dbName += TABLE_SEPARATOR;
                dotIndex++;
            }
            nTbl = dbName + ShadowConstants.PRESSURE_TEST_PREFIX + name.substring(dotIndex + 1);
        } else {
            nTbl = ShadowConstants.PRESSURE_TEST_PREFIX + nTbl;
        }
        shadowTbls.put(name, nTbl);
        return nTbl;
    }

    public static String shadowKey(String key) {
        if (supportOnlineTest) {
            return ShadowConstants.PRESSURE_TEST_PREFIX + key;
        }
        return key;
    }

    public static String checkAndGetShadowKey(String key) {
        if (!RequestContext.isOnlineTest()) {
            return key;
        }
        return shadowKey(key);
    }

    public static String checkAndGetShadowTblName(String name) {
        if (!RequestContext.isOnlineTest() || !supportOnlineTest || shadowDatabase) {
            return name;
        }
        return shadowName(name);
    }

    ////先写死基于影子表，如果要根据影子库的话用注释掉的代码即可
    public static String checkAndGetShadowColName(String name) {
//        if (!RequestContext.isOnlineTest() || !supportOnlineTest || shadowDatabase) {
        if (!RequestContext.isOnlineTest() || !supportOnlineTest) {
            return name;
        }
        return shadowName(name);
    }

    public static boolean isShadowDatabase() {
        return supportOnlineTest && shadowDatabase;
    }

    public static boolean isShadowTable() {
        return supportOnlineTest && !shadowDatabase;
    }
}
